﻿using System.Collections.Generic;
using System.Diagnostics;
using Windows.Foundation;
using Windows.UI.Xaml.Markup;

namespace LayoutHelpers.Primitives
{
  public class WidthLayoutEvaluator : ILayoutEvaluator
  {
    public double Width { get; set; }
    public ComparisonType ComparisonType { get; set; }
    public bool Evaluate(Size availableSize)
    {
      switch (ComparisonType)
      {
        case ComparisonType.LessThan:
          return availableSize.Width < Width;

        case ComparisonType.GreaterThanOrEqualTo:
          return availableSize.Width >= Width;

        default:
          DebugEx.AssertAndBreak("Unknown ComparisonType {0}", ComparisonType);
          return false;
      }
    }
  }

  public class HeightLayoutEvaluator : ILayoutEvaluator
  {
    public double Height { get; set; }
    public ComparisonType ComparisonType { get; set; }
    public bool Evaluate(Size availableSize)
    {
      switch (ComparisonType)
      {
        case ComparisonType.LessThan:
          return availableSize.Height < Height;

        case ComparisonType.GreaterThanOrEqualTo:
          return availableSize.Height >= Height;

        default:
          DebugEx.AssertAndBreak("Unknown ComparisonType {0}", ComparisonType);
          return false;
      }
    }
  }

  public class SizeLayoutEvaluator : ILayoutEvaluator
  {
    public double MinWidth { get; set; }
    public double MaxWidth { get; set; }
    public double MinHeight { get; set; }
    public double MaxHeight { get; set; }

    public SizeLayoutEvaluator()
    {
      MinWidth = MinHeight = 0;
      MaxWidth = MaxHeight = double.PositiveInfinity;
    }

    public bool Evaluate(Size availableSize)
    {
      return availableSize.Width >= MinWidth &&
              availableSize.Width < MaxWidth &&
              availableSize.Height >= MinHeight &&
              availableSize.Height < MaxHeight;
    }
  }

  public class ShapeLayoutEvaluator : ILayoutEvaluator
  {
    public ShapeType ShapeType { get; set; }

    static double SquareEpsilonRatio = 1.05;

    public bool Evaluate(Size availableSize)
    {
      // No match for invisible areas
      if (availableSize.Width == 0 || availableSize.Height == 0)
        return false;

      var ratio = availableSize.Width / availableSize.Height;

      if (ratio > SquareEpsilonRatio)
        return ShapeType == ShapeType.Wide;

      if (ratio < 1.0 / SquareEpsilonRatio)
        return ShapeType == ShapeType.Tall;

      // Either we're within the range of 'square-ness' or ratio is NaN.
      // NaN is caused by either 0.0 / 0.0 or +Inf / +Inf, and
      // we already checked for zero at the top so in any case this is a square
      return ShapeType == ShapeType.Square;
    }
  }

  [ContentProperty(Name = "Rules")]
  public class CompoundLayoutEvaluator : ILayoutEvaluator
  {
    public List<ILayoutEvaluator> Rules { get; private set; }

    public CompoundRuleType RuleType { get; set; }

    public bool Evaluate(Size availableSize)
    {
      foreach (var rule in Rules)
      {
        var result = rule.Evaluate(availableSize);
        if (result == true && RuleType == CompoundRuleType.Or)
          return true;
        if (result == false && RuleType == CompoundRuleType.And)
          return false;
      }

      // Either none of the items matched and this is an 'Or' rule,
      // or all of the items matched and this is an 'And' rule
      return RuleType == CompoundRuleType.And;
    }

    public CompoundLayoutEvaluator()
    {
      Rules = new List<ILayoutEvaluator>();
      RuleType = CompoundRuleType.Or;
    }
  }

  public class DefaultLayoutEvaluator : ILayoutEvaluator
  {
    public bool Evaluate(Size availableSize)
    {
      return true;
    }
  }

  public class AssertLayoutEvaluator : ILayoutEvaluator
  {
    public string Message { get; set; }

    public AssertLayoutEvaluator()
    {
      Message = "Error: unexpected layout condition";
    }

    public bool Evaluate(Size availableSize)
    {
      DebugEx.AssertAndBreak(Message);
      return false;
    }
  }
}